#include "filter_service.h"
#include "log.h"
#include "filters/path_filter.h"
#include "filters/caller_filter.h"
#include "filters/file_filter.h"
#include "filters/custom_filter.h"
#include "string_util.h"

namespace {
const int NAME_LEN_CONVERT_RATIO = 2;
const int FILE_NAME_PREFIX_LEN = 4;
}  // namespace

FilterService::~FilterService()
{
    Stop();
}

void FilterService::AddFilter(const std::shared_ptr<Filter> &filter)
{
    if (filter == nullptr) {
        LOG_INFO("filter is invaild.");
        return;
    }
    filters.push_back(filter);
}

void FilterService::SetChannel(const std::shared_ptr<IoctlChannel> &c)
{
    if (c == nullptr) {
        LOG_INFO("channel is invaild.");
        return;
    }
    this->channel = c;
}

bool FilterService::Start()
{
    if (channel == nullptr || !channel->Connect()) {
        LOG_ERR("channel is not ready.");
        return false;
    }
    LOG_INFO("start filter service");
    isRunning = true;
    Update();
    threadPool.Enqueue([this] {
        while (isRunning) {
            IO_MSG msg{};
            msg.NameMaxLength = MSG_NAME_LENGTH;
            msg.FileName[0] = 1;
            if (channel == nullptr) {
                LOG_ERR("icotl channel is invaild.");
                break;
            }
            if (channel->Recv(msg)) {
                Dispatch(msg);
            }
        }
    });
    return true;
}

void FilterService::Stop()
{
    LOG_INFO("stop filter service.");
    if (!isRunning && channel != nullptr) {
        return;
    }
    isRunning = false;
    channel->Disconnect();
}

bool FilterService::Accept(const ProcessInfo &process) const
{
    LOG_DEBUG("test process: {}, pid: {}, ppid: {}", UnicodeToUtf8(process.fileName), process.pid, process.ppid);
    for (auto &filter : filters) {
        if (filter->Handle(process)) {
            return true;
        }
    }
    return false;
}

void FilterService::OnMessage(IO_MSG &msg)
{
    std::wstring fileName(msg.FileName, msg.NameMaxLength / NAME_LEN_CONVERT_RATIO);
    fileName = fileName.substr(FILE_NAME_PREFIX_LEN);
    switch (msg.Message) {
        case MSG_TYPE_EXE_ASK: {
            ProcessInfo info;
            info.fileName = fileName;
            info.pid = reinterpret_cast<DWORD>(msg.CurrentPid);
            info.ppid = reinterpret_cast<DWORD>(msg.ParentPid);
            if (Accept(info)) {
                msg.Message = MSG_TYPE_EXE_ALLOW;
            } else {
                msg.Message = MSG_TYPE_EXE_DENY;
            }
            break;
        }
        case MSG_TYPE_INSTALL_END: {
            LOG_INFO("update installed app.");
            Update();
            break;
        }
        case MSG_TYPE_DEBUG_ALL:
        default:
            break;
    }
    if (channel == nullptr || !channel->Reply(msg)) {
        LOG_ERR("reply failed.");
    }
}

void FilterService::Dispatch(IO_MSG &msg)
{
    threadPool.Enqueue(std::bind(&FilterService::OnMessage, this, msg));
}

void FilterService::Update()
{
    for (const auto &filter : filters) {
        filter->Update();
    }
}

bool FilterService::Init(FilterConfig &config)
{
    auto pathFilter = std::make_shared<PathFilter>(config.GetTrustedPathList());
    auto callerFilter = std::make_shared<CallerFilter>(config.GetUnTrustedProcessNameList());
    auto fileFilter =
        std::make_shared<FileFilter>(config.GetTrustedProductNameList(), config.GetTrustedFileNamePatternList());
    auto customFilter = std::make_shared<CustomFilter>(config.GetBlockAppList(), config.GetBlockTypes());
    if (pathFilter == nullptr || callerFilter == nullptr || fileFilter == nullptr || customFilter == nullptr) {
        LOG_ERR("init filter failed.");
        return false;
    }
    auto channel = IoctlChannel::GetInstance();
    if (channel == nullptr) {
        LOG_ERR("init channel failed.");
        return false;
    }

    AddFilter(pathFilter);
    AddFilter(callerFilter);
    AddFilter(fileFilter);
    AddFilter(customFilter);
    SetChannel(channel);
    return true;
}